package auth;

import cache.CacheCallBack;
import cache.CacheHelper;
import ext.wxcp.WeixinCpHelper;
import ext.wxcp.WxCpJsapiSignature;
import models.enums.WxcpAppType;
import helper.GlobalConfig;
import me.chanjar.weixin.cp.api.WxCpConfigStorage;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpUser;
import models.admin.AdminUser;
import models.constants.DeletedStatus;
import models.system.Company;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.EnumUtils;
import org.apache.log4j.MDC;
import play.Logger;
import play.Play;
import play.mvc.After;
import play.mvc.Before;
import play.mvc.Controller;
import play.mvc.Scope;
import util.common.RandomNumberUtil;

import java.util.Arrays;

/**
 * 微信企业号认证，基于OAuth.
 *
 * 使用参数 appType 区分.
 */
public class WxCpAuth extends Controller {

    private final static ThreadLocal<AdminUser> _currentUser = new ThreadLocal<>();
    private final static ThreadLocal<WxCpUser> _currentWxCpUser = new ThreadLocal<>();


    /**
     * 检查是否已经OAuth认证过，如果没有，则调用认证.
     *
     * @throws Throwable
     */
    @Before(unless = {"login", "logout", "fail", "validate", "validation", "authenticate", "captcha"})
    public static void filter() throws Throwable {
        //生成日志跟踪ID.
        MDC.put("ttid", RandomNumberUtil.generateRandomChars(8));
        Logger.info("[Auth]: Filter for URL -> " + request.url + ", method=" + request.method);

        if (skipAuthCheck()) {
            Logger.info("[Auth]: Skip the Auth Check.");
            return;
        }

        AdminUser user = getUserFromSession();


        if (user == null) {
            // for test
            Logger.info("LOG00120: user=" + user);
            if (Play.mode.isDev() && request.host.contains("localhost")) {
                Logger.info("LOG03640: user default User");
                user = AdminUser.find("deleted=?1 order by createAt desc",
                        DeletedStatus.UN_DELETED).first();
            }
        }

        String appType = params.get("appType");
        if (appType == null) {
            appType = "WY";
        }

        Logger.info("LOG00120: appType=" + appType);
        WxcpAppType enuAppType = EnumUtils.getEnum(WxcpAppType.class, appType.toUpperCase());
        if (enuAppType == null) {
            enuAppType = WxcpAppType.WY;
        }
        renderArgs.put("appType", enuAppType.toString().toLowerCase());
        if (user == null) {
            WxCpConfigStorage wxCpConfigStorage = WeixinCpHelper.getWxCpConfigStorage(enuAppType);
            if (wxCpConfigStorage == null) {
                Logger.info("找不到" + enuAppType + "对应的CorpApp");
                renderText("不存在的应用地址，请检查域名是否正确.");
                return;
            }
            WxCpService wxCpService = WeixinCpHelper.getWxCpService(wxCpConfigStorage);
            //是否有code&state参数？如果有，就是重定向回来的，否则就是进行重定向到微信OAuth地址.
            String code = params.get("code");
            String state = params.get("state");
            Logger.info("   -----> code=%s, state=%s, accessToken=%s", code, state, wxCpService.getAccessToken());
            if (StringUtils.isBlank(code) && !"STATE2015".equals(state)) {
                // 重定向到OAuth认证地址.
                String targetUrl = "http://" + request.host + request.url;
                //String redirectUrl = wxCpService.oauth2buildAuthorizationUrl("STATE2015");
                String redirectUrl = WeixinCpHelper.oauth2BuildAuthorizationUrl(wxCpConfigStorage, targetUrl, "STATE2015");
                Logger.info("[Auth]: oauth2.redirectUrl -> %s", redirectUrl);
                redirect(redirectUrl);
            } else {
                // 获取用户信息，并检查数据库中是否存在，如果不存在，就自动创建.
                String[] res = wxCpService.oauth2getUserInfo(code);
                try{
                    String wxUserId = res[0];
                    String deviceId = res[1];
                    Logger.info("[Auth]: wxUserId=%s, deviceId=%s", wxUserId, deviceId);
                    WxCpUser wxCpUser = wxCpService.userGet(wxUserId);
                    Logger.info("通过微信接口读取用户信息，以在数据库中建立或更新用户.");
                    Company corp = Company.getByDomain(request.domain);
                    user = WeixinCpHelper.findUserFromWxCpUser(wxCpService, corp, wxCpUser);
                    if (user == null && wxCpUser != null) {
                        _currentWxCpUser.set(wxCpUser);
                    }
                    String sessionId = Scope.Session.current().getId();
                }catch (Exception e) {
                    Logger.warn(e, "error");
                    Logger.info("[Auth]: oauth2.state(%s) not equals session state (%s), bad request!!!", state, session.get("WXMP_STATE"));
                    // 重定向到OAuth认证地址.
                    String targetUrl = "http://" + request.host + request.url;
                    //String redirectUrl = wxCpService.oauth2buildAuthorizationUrl("STATE2015");
                    String redirectUrl = WeixinCpHelper.oauth2BuildAuthorizationUrl(wxCpConfigStorage, targetUrl, "STATE2015");
                    Logger.info("[Auth]: oauth2.redirectUrl -> %s", redirectUrl);
                    redirect(redirectUrl);
                }
            }
        }

        Logger.info("LOG00070: user=" + user);
        if (user != null) {
            //session.clear();
            Logger.info("LOG00080: store session");
            session.put(GlobalConfig.WEIXIN_CP_SESSION_USER_KEY, String.valueOf(user.id));
            renderArgs.put("currentUser", user);
            _currentUser.set(user);
            // 设置JSAPI
            UseJsApi annoUseJsAPI = getUseJsAPI();
            if (annoUseJsAPI != null) {
                Logger.info("LOG00090: setup  use JSAPI");
                WxCpConfigStorage wxCpConfigStorage = WeixinCpHelper.getWxCpConfigStorage(request.host);
                WxCpService wxCpService = WeixinCpHelper.getWxCpService(wxCpConfigStorage);
                String currentUrl = "http://" + request.host + request.url;

                WxCpJsapiSignature jsSignature = WeixinCpHelper.createJsapiSignature(wxCpConfigStorage, wxCpService, currentUrl);
                jsSignature.jsApis = Arrays.asList(annoUseJsAPI.value());
                renderArgs.put("jsSignature", jsSignature);
            }
        }
    }

    @After
    public static void cleanCurrentUser() {
        _currentUser.remove();
    }

    /**
     * 在Controller中使用WexinCpAuth可以得到当前用户.
     */
    public static AdminUser currentUser() {
        return _currentUser.get();
    }

    /**
     * 获得当前的需要绑定的WxCpUser
     * @return
     */
    public static WxCpUser currentWxCpUser() {
        return _currentWxCpUser.get();
    }

    /**
     * 尝试从Session中得到用户信息.
     */
    private static AdminUser getUserFromSession() {
        AdminUser user = null;
        String sessionUserId = session.get(GlobalConfig.WEIXIN_CP_SESSION_USER_KEY);
        Logger.info("sessionUserId=" + sessionUserId);
        if (StringUtils.isNotBlank(sessionUserId)) {
            final String userId = sessionUserId;
            user = CacheHelper.getCache("WxCpUser_" + sessionUserId, (CacheCallBack<AdminUser>) () -> AdminUser.findById(Long.parseLong(userId)));
            if (user == null) {
                Logger.info("user is null or not actived user=" + user);
                session.remove(GlobalConfig.WEIXIN_CP_SESSION_USER_KEY);
            }
        }

        if (user == null && Play.mode == Play.Mode.DEV) {
            // 开发模式放一个测试用户
            user = AdminUser.findAdminUserByLoginName("demo");
        }

        if (user != null) {
            //session.clear();
            session.put(GlobalConfig.WEIXIN_CP_SESSION_USER_KEY, String.valueOf(user.id));
        }

        Logger.info("user=" + user);
        return user;
    }

    /**
     * 检查是否要跳过认证检查.
     *
     * @return 返回true表明跳过检查.
     */
    private static boolean skipAuthCheck() {
        if (getActionAnnotation(SkipAuth.class) != null ||
                getControllerInheritedAnnotation(SkipAuth.class) != null) {
            Logger.info("skipAuthCheck=true");
            return true;
        }
        Logger.info("skipAuthCheck=false");
        return false;
    }

    /**
     * 检查是否要使用JSAPI.
     *
     * 在类名或方法上使用@UseJsAPI标注，会在renderArgs中加入一个jsSignature的对象.
     *
     * @return UseJsAPI，其value指定需要的权限.
     */
    private static UseJsApi getUseJsAPI() {
        if (getActionAnnotation(UseJsApi.class) != null) {
            Logger.info("UseJsAPI on Action");
            return getActionAnnotation(UseJsApi.class);
        }
        if (getControllerInheritedAnnotation(UseJsApi.class) != null) {
            Logger.info("UseJsAPI on Controller");
            return getControllerInheritedAnnotation(UseJsApi.class);
        }
        Logger.info("UseJsAPI=false");
        return null;
    }
}
